// Server side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <chrono>
#include <thread>
#include <cstdint>
#include <fstream>
#include <string>
#include <deque>
#include <mutex>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>

std::mutex                 g_pts_mtx;
std::vector<std::uint64_t> g_pts;

inline std::uint64_t getTimestamp()
{
    return static_cast<std::uint64_t>(
        std::chrono::duration_cast<std::chrono::microseconds>(
            std::chrono::system_clock::now().time_since_epoch())
            .count());
}

#define PORT    2000
#define MAXLINE 1024

// Driver code
int main()
{
    int  sockfd;
    char buffer[MAXLINE];
    // char const*        hello = "Hello from server";
    struct sockaddr_in servaddr, cliaddr;

    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // Filling server information
    servaddr.sin_family      = AF_INET; // IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port        = htons(PORT);

    // Bind the socket with the server address
    if (bind(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    int                        cnt = 0;
    std::vector<std::uint64_t> pts;
    pts.reserve(100000);

    std::thread(
        [&]()
        {
            while (true)
            {
                std::this_thread::sleep_for(std::chrono::seconds(5));
                std::vector<std::uint64_t> tmp_pts;

                g_pts_mtx.lock();
                if (g_pts.size() >= 3278)
                {
                    tmp_pts.swap(g_pts);
                }
                g_pts_mtx.unlock();

                if (tmp_pts.empty())
                {
                    continue;
                }

                std::sort(tmp_pts.begin(), tmp_pts.end());

                auto const cnt = tmp_pts.size();

                auto const idx_pct99 = cnt * 99 / 100;
                auto const idx_pct95 = cnt * 95 / 100;
                auto const idx_pct90 = cnt * 90 / 100;
                auto const idx_pct75 = cnt * 75 / 100;
                auto const idx_pct50 = cnt * 50 / 100;

                for (auto pt : tmp_pts)
                {
                    std::cout << pt << std::endl;
                }

                std::cout << "Total|PctIndexes: " << cnt << "|" << idx_pct99
                          << "|" << idx_pct95 << "|" << idx_pct90 << "|"
                          << idx_pct75 << "|" << idx_pct50 << std::endl;

                std::cout << "pt_pct_100: " << tmp_pts.back() << std::endl;
                std::cout << "pt_pct_99: " << tmp_pts[idx_pct99] << std::endl;
                std::cout << "pt_pct_95: " << tmp_pts[idx_pct95] << std::endl;
                std::cout << "pt_pct_90: " << tmp_pts[idx_pct90] << std::endl;
                std::cout << "pt_pct_75: " << tmp_pts[idx_pct75] << std::endl;
                std::cout << "pt_pct_50: " << tmp_pts[idx_pct50] << std::endl;
            }
        })
        .detach();

    ::timeval tv{};
    tv.tv_sec  = 5;
    tv.tv_usec = 0;

    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    while (true)
    {
        if (cnt >= 3278)
        {
            g_pts_mtx.lock();
            g_pts.swap(pts);
            g_pts_mtx.unlock();
            cnt = 0;

            std::this_thread::sleep_for(std::chrono::hours(24));
        }

        socklen_t len;
        ssize_t   n;

        len = sizeof(cliaddr);

        n = recvfrom(sockfd,
                     buffer,
                     MAXLINE,
                     0,
                     reinterpret_cast<::sockaddr*>(&cliaddr),
                     &len);

        std::cout << ++cnt << "|" << n << std::endl;

        buffer[n] = '\0';

        if (n <= 0)
        {
            std::cout << "recvfrom_Error: " << n << "|" << std::strerror(errno)
                      << std::endl;
            continue;
        }

        auto const start = getTimestamp();
        n                = sendto(sockfd,
                   buffer,
                   static_cast<std::size_t>(n),
                   MSG_CONFIRM,
                   reinterpret_cast<sockaddr const*>(&cliaddr),
                   sizeof(cliaddr));
        pts.push_back(getTimestamp() - start);

        if (n <= 0)
        {
            std::cout << "sendto_Error: " << n << "|" << std::strerror(errno)
                      << std::endl;
            continue;
        }
    }

    return 0;
}